﻿//----------------------------------------------------------------
// Copyright (c) 2011-2013 
// All rights reserved.
// Author: Ben DaoWei 
// Contact: bendaowei@gmail.com
// Web Link: http://xnaeditor.codeplex.com
// ----------------------------------------------------------------

namespace UnityEngine.Graphics
{
    using MdxLib.Model;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework;
    using System.Collections.Generic;
    using System;
    using System.Diagnostics;
    using UnityEngine.IO;
    using System.IO;
    using UnityEngine.Gameplay.Match;

    public class ModelRenderer : GraphicsRenderer
    {
        private List<GeosetRenderer> geosetRenders;

        private List<GeosetAnimationRenderer> geosetAnimationRenderers;

        private List<NodeRenderer> nodeRenderers;  //bones, helpers, attachments, collisionShapes, lights, camera

        private List<int> topologicalIndex;

        public List<GeosetRenderer> GeosetRenderers
        {
            get
            {
                return this.geosetRenders;
            }
        }

        public ModelRenderer(string path, GraphicsDevice device)
            : this(LoadCModel(path), device)
        {
        }

        public ModelRenderer(GraphicsDevice device, CModel model)
            : this(model, device)
        {
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.GeosetRenderers != null)
                {
                    foreach (var geoset in this.GeosetRenderers)
                    {
                        geoset.Dispose();
                    }
                }
            }

            base.Dispose(disposing);
        }

        public ModelRenderer(CModel model, GraphicsDevice device)
            : base(device)
        {
            this.Model = model;

            // initialize geoset renderers
            geosetRenders = new List<GeosetRenderer>();
            foreach (CGeoset geoset in model.Geosets)
            {
                geosetRenders.Add(new GeosetRenderer(geoset, device));
            }

            // initialize geoset animation renderer
            geosetAnimationRenderers = new List<GeosetAnimationRenderer>();
            for (int i = 0; i < model.GeosetAnimations.Count; i++)
                geosetAnimationRenderers.Add(new GeosetAnimationRenderer(model.GeosetAnimations[i], this.geosetRenders));

            // initialize node renders
            InitializeBonesAndHelpersAndAnimationRenderersAndAttachmentsAndCollisionShapesAndLightsAndCameras();

            // create bounding box
            List<Vector3> points = new List<Vector3>();
            foreach (var geosetRenderer in geosetRenders)
            {
                foreach (var point in geosetRenderer.BoundingBox.GetCorners())
                    points.Add(point);
            }
            _BoundingBox = BoundingBox.CreateFromPoints(points);
        }

        public CModel Model
        {
            get;
            set;
        }

        public static CModel LoadCModel(string path)
        {
            CModel model = new CModel();

            using (var Stream = new System.IO.FileStream(path, System.IO.FileMode.Open, System.IO.FileAccess.Read))
            {
                var ModelFormat = new MdxLib.ModelFormats.CMdx();
                ModelFormat.Load(path, Stream, model);
            }

            return model;
        }

        private bool[] visited;
        private void InitializeBonesAndHelpersAndAnimationRenderersAndAttachmentsAndCollisionShapesAndLightsAndCameras()
        {
            nodeRenderers = new List<NodeRenderer>();
            nodeRenderers.Clear();

            for (int i = 0; i < this.Model.Bones.Count; i++)
                nodeRenderers.Add(new BoneRenderer(this.Model.Bones[i]));

            for (int i = 0; i < this.Model.Lights.Count; i++)
                nodeRenderers.Add(new LightRenderer(this.Model.Lights[i]));

            for (int i = 0; i < this.Model.Helpers.Count; i++)
                nodeRenderers.Add(new HelpersRenderer(this.Model.Helpers[i]));

            for (int i = 0; i < this.Model.Attachments.Count; i++)
                nodeRenderers.Add(new AttachmentRenderer(this.Model.Attachments[i]));

            for (int i = 0; i < this.Model.ParticleEmitters.Count; i++)
                nodeRenderers.Add(new ParticleEmitterRenderer(this.Model.ParticleEmitters[i]));

            for (int i = 0; i < this.Model.ParticleEmitters2.Count; i++)
                nodeRenderers.Add(new ParticleEmitter2sRenderer(this.Model.ParticleEmitters2[i]));

            for (int i = 0; i < this.Model.RibbonEmitters.Count; i++)
                nodeRenderers.Add(new RibbonEmitterRenderer(this.Model.RibbonEmitters[i]));

            for (int i = 0; i < this.Model.Events.Count; i++)
                nodeRenderers.Add(new EventObjectRenderer(this.Model.Events[i]));

            for (int i = 0; i < this.Model.CollisionShapes.Count; i++)
                nodeRenderers.Add(new CollisionShapeRenderer(this.Model.CollisionShapes[i]));

            for (int i = 0; i < nodeRenderers.Count; i++)
            {
                if (nodeRenderers[i].NodeId != i)
                    throw new Exception("Model format is incorrect.");
            }

            // clear topological index array
            this.topologicalIndex = new List<int>();

            // clear visited array
            visited = new bool[this.Model.Bones.Count + this.Model.Lights.Count + this.Model.Helpers.Count +
                                this.Model.ParticleEmitters.Count + this.Model.ParticleEmitters2.Count +
                                this.Model.RibbonEmitters.Count + this.Model.Events.Count +
                                this.Model.Attachments.Count + this.Model.CollisionShapes.Count];
            for (int i = 0; i < this.Model.Bones.Count + Model.Helpers.Count; i++)
                visited[i] = false;

            // start topological
            for (int i = 0; i < nodeRenderers.Count; i++)
            {
                TopologicalSort(nodeRenderers[i]);
            }

            if (this.topologicalIndex.Count != nodeRenderers.Count)
                throw new Exception("Count of topological index is incorrect.");

            visited = null;
        }

        private void TopologicalSort(NodeRenderer nodeRenderer)
        {
            // this is not all correct, because attachment and helper, what are added later, is not calculated.
            if (nodeRenderer.NodeId >= visited.Length || nodeRenderer.NodeId < 0)
            {
                throw new Exception("NodeRenderer index is out of range");
                //return;
            }

            if (visited[nodeRenderer.NodeId])
                return;
            else
            {
                if (nodeRenderer.ParentNodeId != -1)
                    TopologicalSort(this.nodeRenderers[nodeRenderer.ParentNodeId]);

                this.topologicalIndex.Add(nodeRenderer.NodeId);
                visited[nodeRenderer.NodeId] = true;
            }
        }

        public void Update(GameTime time)
        {
            //this.Player.Update(time.ElapsedGameTime);
            //int currentTick = this.Player.CurrentTick;
            //if (currentTick >= 0)
            //    UpdateAnimationTick(currentTick);
        }

        public void UpdateAnimationTick(int currentTick)
        {
            // update bone renderers
            for (int nodeIndex = 0; nodeIndex < this.nodeRenderers.Count; nodeIndex++)
            {
                this.nodeRenderers[nodeIndex].Update(currentTick);
            }

            // update world matrix
            for (int i = 0; i < this.nodeRenderers.Count; i++)
            {
                // get current and parent index
                int current = this.topologicalIndex[i];
                int parent = this.nodeRenderers[current].ParentNodeId;

                if (parent != -1)
                {
                    this.nodeRenderers[current].World = this.nodeRenderers[current].World * this.nodeRenderers[parent].World;
                }

                //this.nodeRenderers[current].Draw();
            }

            // update geoset, cpu skinning
            foreach (GeosetRenderer GeosetRenderer in this.geosetRenders)
            {
                GeosetRenderer.NodeRenderers = this.nodeRenderers;
                GeosetRenderer.Update();
            }

            foreach (GeosetAnimationRenderer GeosetAnimationRenderer in this.geosetAnimationRenderers)
            {
                GeosetAnimationRenderer.Update(currentTick);
            }
        }

        public bool Intersects(Ray ray, Matrix world)
        {
            bool ret = false;
            foreach (GeosetRenderer geosetRenderer in this.GeosetRenderers)
            {
                if (geosetRenderer.Intersects(ray, world))
                {
                    ret = true;
                    break;
                }
            }
            return ret;
        }

        public void UpdateRelpacableTexture(Texture2D texture)
        {
            foreach (GeosetRenderer GeosetRenderer in this.GeosetRenderers)
            {
                foreach (MaterialLayerRenderer layerRenderer in GeosetRenderer.MaterialRenderer.LayerRenderers)
                {
                    layerRenderer.TextureRenderer.ReplaceReplaceableTexture(texture);
                }
            }
        }

        public void UpdateTeam(TeamRelationType team)
        {
            foreach (GeosetRenderer GeosetRenderer in this.GeosetRenderers)
            {
                foreach (MaterialLayerRenderer layerRenderer in GeosetRenderer.MaterialRenderer.LayerRenderers)
                {
                    layerRenderer.TextureRenderer.ReplaceReplaceableTexture(team);
                }
            }
        }

        public bool IsTransparent()
        {
            bool isTranparent = false;
            foreach (var geosetRenderer in geosetRenders)
            {
                isTranparent = isTranparent || geosetRenderer.IsTransparent();
            }
            return isTranparent;
        }

        public void Draw(Matrix world, Matrix view, Matrix projection)
        {
            //foreach (var geosetRenderer in geosetRenders)
            //for (int i = 0; i < geosetRenders.Count; i++ )
            for (int i = geosetRenders.Count - 1; i >= 0 ; i-- )
            {
                var geosetRenderer = geosetRenders[i];
                geosetRenderer.Draw(world, view, projection);
            }
        }

        public VertexBuffer[] VertexBuffers
        {
            get
            {
                VertexBuffer[] buffers = new VertexBuffer[geosetRenders.Count];
                int index = 0;
                foreach (var geosetRenderer in geosetRenders)
                {
                    buffers[index] = geosetRenderer.VertexBuffer;
                    index++;
                }
                return buffers;
            }
        }

        private BoundingBox _BoundingBox;
        public BoundingBox BoundingBox
        {
            get
            {
                return _BoundingBox;
            }
        }
    }
}
